#include <stdio.h>
#include <errno.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include <sys/mman.h>
#include <poll.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <linux/videodev2.h>

#define DEV                     "/dev/video0"
#define BUFFER_COUNT            4

int main()
{
    int fd;
    int fp;
    int ret = 0;
    
    struct v4l2_capability cap;
    struct v4l2_input input;
    struct v4l2_fmtdesc fmtdesc;
    struct v4l2_format v4l2_fmt;
    struct v4l2_requestbuffers req;
    struct v4l2_buffer v4l2_buffer;
    void* addr[BUFFER_COUNT];
    struct pollfd poll_fds[1];
    
    //创建图片文件
    fp = open("./pic.yuv", O_CREAT | O_TRUNC | O_RDWR, 0666 );
    if(fp < 0)
    {
        printf("failed to open picture\n");
        ret = -1;
        goto EXIT;
    }
    
    //打开设备
    fd = open(DEV, O_RDWR);
    if(fd < 0)
    {
        printf("v4l2_open fail\n");
        goto EXIT;
    }
    
    //查询设备支持的功能
    ret = ioctl(fd, VIDIOC_QUERYCAP, &cap);
    if (ret < 0)
    {
        printf("ioctl VIDIOC_QUERYCAP fail\n");
        goto EXIT;
    }
    
    //printf("cap:%d\n", cap.capabilities);
    
    if(cap.capabilities & V4L2_CAP_VIDEO_CAPTURE)
        printf("dev support capture\n");
    
    if(cap.capabilities & V4L2_CAP_VIDEO_OUTPUT)
        printf("dev support output\n");
    
    if(cap.capabilities & V4L2_CAP_VIDEO_OVERLAY)
        printf("dev support overlay\n");
    
    if(cap.capabilities & V4L2_CAP_STREAMING)
        printf("dev support streaming\n");
    
    if(cap.capabilities & V4L2_CAP_READWRITE)
        printf("dev support read write\n");


    //枚举所有输入源(一个设备可能有多个输入)
    input.index = 0;
    while (!ioctl(fd, VIDIOC_ENUMINPUT, &input))
    {
        printf("input:%s\n", input.name);
        ++input.index;
    }
    
    //选择输入源
    int index = 0;
    if (ioctl(fd, VIDIOC_S_INPUT, &index) < 0)
    {
        printf("VIDIOC_S_INPUT failed\n");
        ret = -1;
        goto EXIT;
    }

    
    //枚举支持的像素格式
    fmtdesc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    fmtdesc.index = 0;

    while (!ioctl(fd, VIDIOC_ENUM_FMT, &fmtdesc))
    {
        printf("fmt:%s\n", fmtdesc.description);
        fmtdesc.index++;
    }

    //设置像素格式
    memset(&v4l2_fmt, 0, sizeof(struct v4l2_format));
    v4l2_fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; 
    v4l2_fmt.fmt.pix.width = 640; //宽度
    v4l2_fmt.fmt.pix.height = 480; //高度
    v4l2_fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_YUYV; //像素格式
    v4l2_fmt.fmt.pix.field = V4L2_FIELD_ANY;

    if (ioctl(fd, VIDIOC_S_FMT, &v4l2_fmt) < 0)
    {
        printf("VIDIOC_S_FMT failed\n");
        ret = -1;
        goto EXIT;
    }

    //申请缓存
    req.count = BUFFER_COUNT; //缓存数量
    req.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    req.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_REQBUFS, &req) < 0)
    {
        printf("VIDIOC_REQBUFS failed\n");
        ret = -1;
        goto EXIT;
    }

    //映射缓存, 将所有缓存放入队列
    int i;
    for(i = 0; i < BUFFER_COUNT; i++)
    {
        memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
        v4l2_buffer.index = i; //想要查询的缓存
        v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        v4l2_buffer.memory = V4L2_MEMORY_MMAP;

        /* 查询缓存信息 */
        ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
        if(ret < 0)
        {
            printf("Unable to query buffer.\n");
            ret = -1;
            goto EXIT;
        }

        /* 映射 */
        addr[i] = mmap(NULL /* start anywhere */ ,
                    v4l2_buffer.length, PROT_READ | PROT_WRITE, MAP_SHARED,
                    fd, v4l2_buffer.m.offset);
        
        //将缓存放入队列
        ret = ioctl(fd, VIDIOC_QBUF, &v4l2_buffer);
        if(ret < 0)
        {
            printf("Unable to queue buffer.\n");
            ret = -1;
            goto EXIT;
        }
    }
    
    //打开Camera
    enum v4l2_buf_type type = V4L2_BUF_TYPE_VIDEO_CAPTURE;

    if (ioctl(fd, VIDIOC_STREAMON, &type) < 0)
    {
        printf("VIDIOC_STREAMON failed\n");
        ret = -1;
        goto EXIT;
    }
    
    //poll等待数据
    poll_fds[0].fd = fd;
    poll_fds[0].events = POLLIN;

    ret = poll(poll_fds, 1, 10000);
    if (ret < 0)
    {
        printf("poll error\n");
        ret = -1;
        goto EXIT;
    }
    else if (ret == 0)
    {
        printf("No data in 10 secs..\n");
        ret = -1;
        goto EXIT;
    }
    
    //出队列
    memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
    v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
    v4l2_buffer.memory = V4L2_MEMORY_MMAP;

    if (ioctl(fd, VIDIOC_DQBUF, &v4l2_buffer) < 0)
    {
        printf("VIDIOC_DQBUF failed, dropped frame\n");
        ret = -1;
        goto EXIT;
    }

    //输出数据到文件
    ret = write(fp, addr[v4l2_buffer.index], v4l2_buffer.length);
    printf("write byte:%d\n", ret);
    
    //重新入队列
    if (ioctl(fd, VIDIOC_QBUF, &v4l2_buffer) < 0)
    {
        printf("VIDIOC_QBUF failed\n");
        ret = -1;
        goto EXIT;
    }

    /***********************释放资源****************************/
    //关闭Camera
    if (ioctl(fd, VIDIOC_STREAMOFF, &type) < 0)
    {
        printf("VIDIOC_STREAMOFF failed\n");
        ret = -1;
        goto EXIT;
    }
    //取消映射
    for(i = 0; i < BUFFER_COUNT; i++)
    {
        memset(&v4l2_buffer, 0, sizeof(struct v4l2_buffer));
        v4l2_buffer.index = i; //想要查询的缓存
        v4l2_buffer.type = V4L2_BUF_TYPE_VIDEO_CAPTURE;
        v4l2_buffer.memory = V4L2_MEMORY_MMAP;

        /* 查询缓存信息 */
        ret = ioctl(fd, VIDIOC_QUERYBUF, &v4l2_buffer);
        if(ret < 0)
        {
            printf("Unable to query buffer.\n");
            ret = -1;
            goto EXIT;
        }
        
        munmap(addr[i], v4l2_buffer.length);
    }

    close(fd);
    close(fp);
    
    
EXIT:
    printf("main exit\n");
    return ret;
}